using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;

namespace Wrath.Xml
{
    public enum ElementType
    {
        Definition,
        Comment,
        Element
    }

    [Serializable]
    public class Element : NameValueBase
    {
        protected List<Attribute> attributes;
        public List<Attribute> Attributes { get { return attributes; } }

        protected List<Element> childElements;

        public List<Element> ChildElements { get { return childElements; } }

        protected Element parentElement;
        public Element Parent { get { return parentElement; } set { parentElement = value; } }

        protected ElementType elementType;
        public ElementType Type { get { return elementType; } set { elementType = value; } }

        public bool IsStarted = false;
        public bool IsEnded = false;

        public Element()
        {
            name = "";
            value = "";
            elementType = ElementType.Element;

            attributes = new List<Attribute>();
            childElements = new List<Element>();

            IsStarted = false;
            IsEnded = false;
        }

        public Element(string name)
        {
            this.name = name;
            value = "";

            attributes = new List<Attribute>();
            childElements = new List<Element>();

            elementType = ElementType.Element;

            IsStarted = false;
            IsEnded = false;
        }

        public Element(string name, string value)
        {
            this.name = name;
            this.value = value;

            attributes = new List<Attribute>();
            childElements = new List<Element>();

            elementType = ElementType.Element;

            IsStarted = false;
            IsEnded = false;
        }

        public Element(string name, string value, ElementType type)
        {
            this.name = name;
            this.value = value;

            attributes = new List<Attribute>();
            childElements = new List<Element>();

            elementType = type;

            IsStarted = false;
            IsEnded = false;
        }

        #region Find Methods

        public Attribute FindAttribute(string attributeName)
        {
            Attribute nameAttribute = attributes.Find(
                new Predicate<Attribute>(
                    delegate(Attribute attribute)
                    {
                        if (attribute.Name.ToLower() == attributeName.ToLower())
                            return true;

                        return false;
                    }
                ));

            return nameAttribute;
        }

        public Element FindFirstElement(string elementName, bool recurse)
        {
            Element elementFound = null;

            if (!recurse)
            {
                elementFound = ChildElements.Find(
                new Predicate<Element>(
                    delegate(Element element)
                    {
                        if (element.Name.ToLower() == elementName.ToLower())
                            return true;

                        return false;
                    }
                ));
            }
            else
            {
                foreach (Element element in ChildElements)
                {
                    if (element.Name.ToLower() == elementName.ToLower())
                    {
                        elementFound = element;
                        break;
                    }

                    elementFound = element.FindFirstElement(elementName, true);
                    if (elementFound != null)
                    {
                        break;
                    }
                }
            }

            return elementFound;
        }

        public Element FindFirstElement(string elementName, string attributeName, bool recurse)
        {
            Element elementFound = null;

            if (!recurse)
            {
                elementFound = ChildElements.Find(
                new Predicate<Element>(
                    delegate(Element currentElement)
                    {
                        if (currentElement.Name == elementName)
                        {
                            Attribute attribute = currentElement.FindAttribute(attributeName);
                            if (attribute != null && attribute.Name == attributeName)
                                return true;
                        }

                        return false;
                    }
                ));
            }
            else
            {
                foreach (Element element in ChildElements)
                {
                    if (element.Name.ToLower() == elementName.ToLower())
                    {
                        Attribute attribute = element.FindAttribute(attributeName);
                        if (attribute != null && attribute.Name == attributeName)
                        {
                            elementFound = element;
                            break;
                        }
                    }

                    elementFound = element.FindFirstElement(elementName, attributeName, true);
                    if (elementFound != null)
                    {
                        break;
                    }
                }
            }

            return elementFound;
        }

        public Element FindFirstElement(string elementName, string attributeName, string attributeValue, bool recurse)
        {
            Element elementFound = null;

            if (!recurse)
            {
                elementFound = ChildElements.Find(
                new Predicate<Element>(
                    delegate(Element currentElement)
                    {
                        if (currentElement.Name == elementName)
                        {
                            Attribute attribute = currentElement.FindAttribute(attributeName);
                            if (attribute != null && attribute.Name == attributeName && attribute.Value == attributeValue)
                                return true;
                        }

                        return false;
                    }
                ));
            }
            else
            {
                foreach (Element element in ChildElements)
                {
                    if (element.Name.ToLower() == elementName.ToLower())
                    {
                        Attribute attribute = element.FindAttribute(attributeName);
                        if (attribute != null && attribute.Name == attributeName && attribute.Value == attributeValue)
                        {
                            elementFound = element;
                            break;
                        }
                    }

                    elementFound = element.FindFirstElement(elementName, attributeName, attributeValue, true);
                    if (elementFound != null)
                    {
                        break;
                    }
                }
            }

            return elementFound;
        }

        public Element FindFirstElementWithAttribute(string attributeName, string attributeValue, bool recurse)
        {
            Element elementFound = null;

            if (!recurse)
            {
                elementFound = ChildElements.Find(
                new Predicate<Element>(
                    delegate(Element currentElement)
                    {
                        Attribute attribute = currentElement.FindAttribute(attributeName);
                        if (attribute != null && attribute.Name == attributeName && attribute.Value == attributeValue)
                            return true;

                        return false;
                    }
                ));
            }
            else
            {
                foreach (Element element in ChildElements)
                {
                    Attribute attribute = element.FindAttribute(attributeName);
                    if (attribute != null && attribute.Name == attributeName && attribute.Value == attributeValue)
                    {
                        elementFound = element;
                        break;
                    }

                    elementFound = element.FindFirstElementWithAttribute(attributeName, attributeValue, true);
                    if (elementFound != null)
                    {
                        break;
                    }
                }
            }

            return elementFound;
        }

        public List<Element> FindAll(string elementName, bool recurse)
        {
            List<Element> elements = new List<Element>();

            if (!recurse)
            {
                elements = ChildElements.FindAll(
                new Predicate<Element>(
                    delegate(Element currentElement)
                    {
                        if (currentElement.Name == elementName)
                            return true;

                        return false;
                    }
                ));
            }
            else
            {
                foreach (Element element in ChildElements)
                {
                    if (element.Name.ToLower() == elementName.ToLower())
                    {
                        elements.Add(element);
                    }

                    if (element.ChildElements.Count > 0)
                    {
                        List<Element> childMatches = element.FindAll(elementName, true);
                        if (childMatches.Count > 0)
                        {
                            elements.AddRange(childMatches);
                        }
                    }
                }
            }

            return elements;
        }

        public List<Element> FindAll(string elementName, string attributeName, bool recurse)
        {
            List<Element> elements = new List<Element>();

            if (!recurse)
            {
                elements = ChildElements.FindAll(
                new Predicate<Element>(
                    delegate(Element currentElement)
                    {
                        if (currentElement.Name == elementName)
                        {
                            Attribute attribute = currentElement.FindAttribute(attributeName);
                            if (attribute != null && attribute.Name == attributeName)
                                return true;
                        }

                        return false;
                    }
                ));
            }
            else
            {
                foreach (Element element in ChildElements)
                {
                    if (element.Name.ToLower() == elementName.ToLower())
                    {
                        Attribute attribute = element.FindAttribute(attributeName);
                        if (attribute != null && attribute.Name == attributeName)
                        {
                            elements.Add(element);
                        }
                    }

                    if (element.ChildElements.Count > 0)
                    {
                        List<Element> childMatches = element.FindAll(elementName, attributeName, true);
                        if (childMatches.Count > 0)
                        {
                            elements.AddRange(childMatches);
                        }
                    }
                }
            }

            return elements;
        }

        public List<Element> FindAll(string elementName, string attributeName, string attributeValue, bool recurse)
        {
            List<Element> elements = new List<Element>();

            if (!recurse)
            {
                elements = ChildElements.FindAll(
                new Predicate<Element>(
                    delegate(Element currentElement)
                    {
                        if (currentElement.Name == elementName)
                        {
                            Attribute attribute = currentElement.FindAttribute(attributeName);
                            if (attribute != null && attribute.Name == attributeName && attribute.Value == attributeValue)
                                return true;
                        }

                        return false;
                    }
                ));
            }
            else
            {
                foreach (Element element in ChildElements)
                {
                    if (element.Name.ToLower() == elementName.ToLower())
                    {
                        Attribute attribute = element.FindAttribute(attributeName);
                        if (attribute != null && attribute.Name == attributeName && attribute.Value == attributeValue)
                        {
                            elements.Add(element);
                        }
                    }

                    if (element.ChildElements.Count > 0)
                    {
                        List<Element> childMatches = element.FindAll(elementName, attributeName, true);
                        if (childMatches.Count > 0)
                        {
                            elements.AddRange(childMatches);
                        }
                    }
                }
            }

            return elements;
        }

        public List<Element> FindAllWithAttribute(string attributeName, bool recurse)
        {
            List<Element> elements = new List<Element>();

            if (!recurse)
            {
                elements = ChildElements.FindAll(
                    new Predicate<Element>(
                        delegate(Element currentElement)
                        {
                            Attribute attribute = currentElement.FindAttribute(attributeName);
                            if (attribute != null && attribute.Name == attributeName)
                            {
                                return true;
                            }

                            return false;
                        }
                    ));
            }
            else
            {
                foreach (Element element in ChildElements)
                {
                    Attribute attribute = element.FindAttribute(attributeName);
                    if (attribute != null && attribute.Name == attributeName)
                    {
                        elements.Add(element);
                    }                   

                    if (element.ChildElements.Count > 0)
                    {
                        List<Element> childMatches = element.FindAllWithAttribute(attributeName, true);
                        if (childMatches.Count > 0)
                        {
                            elements.AddRange(childMatches);
                        }
                    }
                }
            }

            return elements;
        }

        public List<Element> FindAllWithAttribute(string attributeName, string attributeValue, bool recurse)
        {
            List<Element> elements = new List<Element>();

            if (!recurse)
            {
                elements = ChildElements.FindAll(
                    new Predicate<Element>(
                        delegate(Element currentElement)
                        {
                            Attribute attribute = currentElement.FindAttribute(attributeName);
                            if (attribute != null && attribute.Name == attributeName && attribute.Value == attributeValue)
                            {
                                return true;
                            }

                            return false;
                        }
                    ));
            }
            else
            {
                foreach (Element element in ChildElements)
                {
                    Attribute attribute = element.FindAttribute(attributeName);
                    if (attribute != null && attribute.Name == attributeName && attribute.Value == attributeValue)
                    {
                        elements.Add(element);
                    }

                    if (element.ChildElements.Count > 0)
                    {
                        List<Element> childMatches = element.FindAllWithAttribute(attributeName, attributeValue, true);
                        if (childMatches.Count > 0)
                        {
                            elements.AddRange(childMatches);
                        }
                    }
                }
            }

            return elements;
        }

        #endregion

        #region ToString

        public override string ToString()
        {
            return this.ToString(0);
        }

        public string ToString(int baseTabLevel)
        {
            string returnString = "";

            for (int i = 0; i < baseTabLevel; i++) returnString += "\t";

            returnString += "<" + this.name;

            if (this.elementType == ElementType.Definition) returnString += "?xml";
            else if (this.elementType == ElementType.Comment) returnString += "!-- ";

            if (this.elementType == ElementType.Definition || this.elementType == ElementType.Element)
            {
                foreach (Attribute attribute in attributes)
                {
                    returnString += " ";
                    returnString += attribute.Name;
                    returnString += "=\"";
                    returnString += attribute.Value;
                    returnString += "\"";
                }
            }

            if (this.elementType == ElementType.Definition)
            {
                returnString += " ?>\r\n";
            }
            else if (this.elementType == ElementType.Comment)
            {
                returnString += this.Value;
                returnString += " -->\r\n";
            }
            else
            {

                if (String.IsNullOrEmpty(this.Value) && this.childElements.Count < 1)
                {
                    returnString += " />\r\n";
                }
                else
                {
                    returnString += ">";

                    bool endOnNewLine = false;

                    if (this.childElements.Count > 0)
                    {
                        endOnNewLine = true;
                        returnString += "\r\n";

                        foreach (Element element in this.childElements)
                        {
                            returnString += element.ToString(baseTabLevel + 1);
                        }
                    }
                    else if (!String.IsNullOrEmpty(this.Value))
                    {
                        int newLineIndex = this.Value.IndexOf("\r\n");
                        if (newLineIndex > -1)
                        {
                            returnString += "\r\n";
                        }

                        returnString += this.Value;

                        if (this.Value.Length > 100 || newLineIndex > -1)
                        {
                            returnString += "\r\n";
                            endOnNewLine = true;
                        }
                    }

                    if (endOnNewLine)
                    {
                        for (int i = 0; i < baseTabLevel; i++) returnString += "\t";
                    }

                    returnString += "</" + this.name + ">\r\n";
                }
            }

            return returnString;
        }

        #endregion


        public Element Copy()
        {
            Element newElement = new Element();

            newElement.Name = String.Copy(name);
            newElement.Value = String.Copy(value);
            newElement.Type = elementType;
            newElement.Parent = parentElement;

            foreach (Attribute attrib in attributes)
            {
                Attribute newAttrib = attrib.Copy();
                newElement.Attributes.Add(newAttrib);
            }

            foreach (Element element in childElements)
            {
                Element childElement = element.Copy();
                newElement.ChildElements.Add(childElement);
            }            

            return newElement;
        }

        public static string XmlEncode(string source, bool extended)
        {
            string newValue = source;

            newValue = newValue.Replace("&", "&amp;");
            newValue = newValue.Replace("<", "&lt;");
            newValue = newValue.Replace(">", "&gt;");
            newValue = newValue.Replace("'", "&apos;");
            newValue = newValue.Replace("\"", "&quot;");

            // Additional replacements to preserve whitespace
            if (extended)
            {
                newValue = newValue.Replace("\t", "&#09;");
                newValue = newValue.Replace("\n", "&#10;");
                newValue = newValue.Replace("\r", "&#13;");
                newValue = newValue.Replace(" ", "&#32;");
            }

            return newValue;
        }

        public static string XmlDecode(string source)
        {
            string newValue = source;

            newValue = newValue.Replace("&lt;", "<");
            newValue = newValue.Replace("&gt;", ">");
            newValue = newValue.Replace("&apos;", "'");
            newValue = newValue.Replace("&quot;", "\"");
            
            // Additional replacements to preserve whitespace
            newValue = newValue.Replace("&#09;", "\t");
            newValue = newValue.Replace("&#10;", "\n");
            newValue = newValue.Replace("&#13;", "\r");
            newValue = newValue.Replace("&#32;", " ");
            
            // Restore ampersands
            newValue = newValue.Replace("&amp;", "&");

            return newValue;
        }
    }
}
